using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using TakePhotoTogether.Communication;
using TakePhotoTogether.Communication.Protocols;
using TakePhotoTogether.Dao;
using TakePhotoTogether.ImageProcessing;
using TakePhotoTogether.Model;

namespace TakePhotoTogether
{
    /// <summary>
    /// Class that manages sync operations
    /// </summary>
    class SyncManager : IRemoteDataListener
    {
        private const string DEBUG_TAG = "TakePhotoTogether.SyncManager => ";
        /// <summary> Represents the last image frame received from remote partner </summary>
        public CVImage ReceivedImage { get; private set; }

        /// <summary> Worker that handles sync loop control </summary>
        private readonly BackgroundWorker backgroundWorker = new BackgroundWorker();

        /// <summary> Singleton instance </summary>
        private static SyncManager instance = null;
        private readonly SocketManager socketManager;

        private string userIdentifier = "";

        private SyncPictureQueue syncPictureQueue = null;

        private readonly CVImage receivedPhotoFull = new CVImage(640, 360);
        private readonly CVImage receivedPhotoSplit = new CVImage(800, 480);
        private readonly CVImage loadedImage = new CVImage(800, 480);

        /// <summary>
        /// Get Singleton instance
        /// </summary>
        /// <returns></returns>
        public static SyncManager GetSyncManager()
        {
            if (instance == null)
            {
                instance = new SyncManager();
            }
            return instance;
        }

        /// <summary>
        /// Main constructor.
        /// It was made private because its instances can be gathered only from singleton method
        /// </summary>
        private SyncManager()
        {
            socketManager = SocketManager.GetSocketManager();
            // Add itself as a listener for incoming sync packets
            socketManager.AddDataListener(this, (int)MessageTypes.SyncPacket);
            socketManager.AddDataListener(this, (int)MessageTypes.IdentificationPacket);
            socketManager.AddDataListener(this, (int)MessageTypes.TakePhotoPacket);
            socketManager.AddDataListener(this, (int)MessageTypes.SyncPhotoACKPacket);

            // Setup worker that handlers sync loop control
            backgroundWorker.WorkerReportsProgress = false;
            backgroundWorker.WorkerSupportsCancellation = true;
            backgroundWorker.DoWork += new DoWorkEventHandler(syncControlLoop);

            // Setup remote semaphore listener
            //RemoteSemaphore.HasToken();

            SetupUserId("userId");
        }

        public void SetupUserId(string userId)
        {
            Debug.WriteLine(DEBUG_TAG + "SetupUserId - " + userId);

            userIdentifier = userId;
            
            // Load queue related to received userIdentifier
            syncPictureQueue = SyncPictureDao.GetInstance().GetPicturesBy(userIdentifier);

            syncPictureQueue.Reload();

            Debug.WriteLine(DEBUG_TAG +"SetupUserId - load sync picture queue with size " + syncPictureQueue.Count);
        }

        /// <summary>
        /// Receive incoming packets
        /// </summary>
        /// <param name="data"></param>
        public void ReceiveData(PayloadData data)
        {
            if (data is SyncData)
            {
                SyncData photoData = (SyncData)data;

                // Save received image
                CVImage ReceivedImage = null;

                if (photoData.width == 800 && photoData.height == 480)
                {
                    receivedPhotoSplit.LoadJpeg(photoData.data);
                    ReceivedImage = receivedPhotoSplit;
                }
                else if (photoData.width == 640 && photoData.height == 360)
                {
                    receivedPhotoFull.LoadJpeg(photoData.data);
                    ReceivedImage = receivedPhotoFull;
                }
                else
                {
                    ReceivedImage = new CVImage(photoData.width, photoData.height, photoData.data);
                }

                ReceivedImage.SavePhoto();

                PlaceHolderManager.MarkPlaceHolderToBeRemoved(photoData.PhotoFileName);
                
                DataPacketFactory.GetSyncPhotoACKData(photoData.PhotoFileName).Send();

                Debug.WriteLine(DEBUG_TAG + "ReceiveData - received photo " + photoData.PhotoFileName);
            }
            else if (data is IdentificationData)
            {
                Debug.WriteLine(DEBUG_TAG + "ReceiveData - received IdenficationData ");

                IdentificationData identificationData = (IdentificationData)data;
                Debug.WriteLine(DEBUG_TAG + "ReceiveData - got identifier from remote: " + identificationData.Identifier);
                SetupUserId(identificationData.Identifier);

                //Recreate placeholders in remote
                string[] imagesToSync = syncPictureQueue.picturePathList.ToArray();

                foreach (string image in imagesToSync)
                {
                    DataPacketFactory.GetTakePhotoData(image).Send();
                }

                StartSyncLoop();
            }
            else if (data is TakePhotoData)
            {
                TakePhotoData takePhotoData = (TakePhotoData)data;

                Debug.WriteLine(DEBUG_TAG + "ReceiveData - remote device taken a photo " + takePhotoData.PhotoFileName);

                PlaceHolderManager.AddPlaceHolder(takePhotoData.PhotoFileName);
            }
            else if (data is SyncPhotoACKData)
            {
                SyncPhotoACKData ack = (SyncPhotoACKData)data;
                Debug.WriteLine(DEBUG_TAG + "ReceiveData - received confirmation that remote device saved last sync photo: " + ack.PhotoFileName);
                syncPictureQueue.ConfirmedPhoto(ack.PhotoFileName);
                syncPictureQueue.Save();
            }
        }

        public void StartSyncLoop()
        {
            if (!backgroundWorker.IsBusy)
            {
                backgroundWorker.RunWorkerAsync();
            }
        }

        public void StopSyncLoop()
        {
            if (backgroundWorker.IsBusy)
                backgroundWorker.CancelAsync();
        }

        /// <summary>
        /// Sync loop. Method used by background worker
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void syncControlLoop(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            const int syncPacket = (int)MessageTypes.SyncPacket;

            while (syncPictureQueue.Count > 0)
            {
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }

                Debug.WriteLine(DEBUG_TAG + "syncControlLoop - queue size " + syncPictureQueue.Count);

                if (socketManager.GetNumberOfPacketsOnQueue(syncPacket) == 0)
                {
                    syncPhoto();
                }

                Thread.Sleep(200);
            }
        }

        /// <summary>
        /// Sync current photo from Queue
        /// </summary>
        private void syncPhoto()
        {
            if (syncPictureQueue.Count > 0 /* && RemoteSemaphore.Request()*/)
            {
                string photoName = syncPictureQueue.Dequeue();
                syncPictureQueue.Save();

                Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    Debug.WriteLine(DEBUG_TAG + "SyncPhoto - dequeue " + photoName);

                    if (!String.IsNullOrEmpty(photoName))
                    {
                        CVImage image = CVImage.LoadCVImage(photoName);

                        SyncPictureToRemote(image, photoName);
                    }
                });
            }
        }

        /// <summary>
        /// Send picture as packets to remote device.
        /// </summary>
        /// <param name="image"></param>
        /// <returns></returns>
        public void SyncPictureToRemote(CVImage image, string filename)
        {
            if (image == null)
            {
                return;
            }

            DataPacket packet = DataPacketFactory.GetSyncPacket(image.ToBytes(), image.Width, image.Height, filename);
            packet.SlicingPackets(30).Send();
        }

        /// <summary>
        /// Add path to Queue of images to be synced
        /// </summary>
        /// <param name="photoName"></param>
        /// <returns></returns>
        public async Task AddToSyncQueue(string photoName)
        {
            DataPacketFactory.GetTakePhotoData(photoName).Send();

            Debug.WriteLine(DEBUG_TAG + "AddToSyncQueue - " + photoName);

            syncPictureQueue.Enqueue(photoName);
        }
    }

    public static class CVImageSyncExtensions
    {
        public static void SyncPhoto(this CVImage image)
        {
            SyncManager.GetSyncManager().AddToSyncQueue(image.LastSavedName);
            SyncManager.GetSyncManager().SyncPictureToRemote(image, image.LastSavedName);
        }
    }
}
